Использование речевых технологий Яндекса на примере аудиосообщений Telegram или чат-бот для распознавания аудиосообщений

В данной статье мы рассмотрим применение речевых технологий, предоставленных компанией Яндекс в контексте распознавания аудиосообщений в Telegram – популярном мессенджере, объединяющем миллионы пользователей по всему миру.

Оглавление

  1. Регистрируемся и получаем API-ключ

  2. Получаем токен для чат-бота

  3. Пишем основу чат-бота

  4. Посылаем запросы в Яндекс.Облако

  5. Дорабатываем чат-бота и смотрим на результат

Изначально функционал получения текст из аудио мне нужен был для проекта, о котором я писал здесь и здесь, где я хотел реализовать ввод продуктов в дневник питания не только текстом но путем отправки аудиосообщения. Кому интересно — можете затестить данный функционал тут. Получилось довольно интересно:

https://t.me/lifestyle_tracker_bot

Данный функционал нам великодушно и альтруистично (почти) предоставила компания Яндекс на их сервисе Яндекс.Облако (Yandex.Cloud). На первый взгляд, название может напоминать о неком облачном хранилище для бесконечных потоков фотографий с телефона, но на самом деле все куда интереснее:

Яндекс.Облако — это сервис облачных вычислений, предоставляемый компанией Яндекс. Он позволяет людям и компаниям арендовать виртуальные серверы и ресурсы для хранения данных, запуска приложений и выполнения вычислений через интернет. Это как аренда виртуального пространства на компьютерах компании Яндекс, чтобы использовать их мощности для своих целей без необходимости покупки и поддержки собственного оборудования.

На Яндекс.Облаке можно найти кучу различных программистских интересностей — распознавание/генерация аудио, машинный перевод, нейросети, базы данных и т.д. Полный список актуальных решений можно посмотреть здесь или посмотреть их список, взятый из википедии ниже:

https://ru.wikipedia.org/wiki/Yandex_Cloud

Да, каждый сервис платный, но их стоимость, в большинстве решений, не столь высока, да и Яндекс предоставляет тестовый период и грант для новых пользователей. Подробные условия их получения можно посмотреть здесь и здесь. Говоря кратко — если вы только зарегистрировались в Яндекс.Облаке, то нате 2 месяца бесплатного доступа, чтобы все затестить.

Театр начинается с вешалки

Переходим на Яндекс.Облако, авторизируемся и попадаем в консоль. После чего сразу создаем здесь платежный аккаунт. Если вы вошли сюда в первый раз, то получаем пробный период/грант или не заморачиваемся и пополняем счет на пару десятков рублей, привязав банковскую карту.

Следующее, что нам нужно сделать — это создать сервисный аккаунт. Сервисный аккаунт в Яндекс.Облаке — это как виртуальная личность для программы или сервиса, которую можно создать, чтобы позволить ей использовать ресурсы и функции облачных серверов без необходимости использовать личный аккаунт. Это позволяет приложениям и программам работать в облаке, делая их доступ более безопасным и удобным, и изолируя их от других пользователей.

Для этого переходим на данную вкладку:

Далее в данном разделе жмем на кнопку «Создать сервисный аккаунт«. Вводим название и выбираем роль. В данном примере мы будем использовать функционал по распознавания аудио — Speech-To-Text (STT), поэтому в качестве роли выберем «ai.speechkit-stt.user«.

Жмем «Создать» и после недолгой прогрузки увидим новую строку в списке сервисных аккаунтов на этой же странице. Нажимаем на эту строчку и попадаем на новую страницу, где в правой верхней части экрана находим кнопку «Создать новый ключ«, нажимаем и выбираем «API-ключ». Появится форма, в которой можно задать описание для данного ключа — нажимаем «Создать» и получаем свежий ключ:

Готово! Сохраняем себе данный ключ или не закрываем данную форму, чтобы потом его скопировать в программу.

Заключаем сделку с BotFather

Чтобы затестить функционал сервиса давайте соберем простейшего чат-бота для Telegram, который будет расшифровывать аудиосообщения и присылать нам текст.

Для начала пойдем к BotFather и, склонив колено, попросим токен для нового чат-бота:

  1. Запускаем бота BotFather

  2. Вызываем команду /newbot

  3. Вводим название для нового бота

  4. Вводим его username

  5. Получаем токен

Теперь перейдем к основной части представления и начнем писать код чат-бота.

Тык тык тык делаю по клавишам

Для создания чат-бота воспользуемся библиотекой telebot (или pyTelegramBotAPI), которую установим таким образом:

pip install pyTelegramBotAPI

Сперва создадим файл «config.py«, где будем хранить все полученные ключи и экземпляр класса TeleBot (импортированный из библиотеки telebot), с помощью которого будут происходить все взаимодействия с чат-ботом. В конструктор данного класса передаем лишь только токен, который мы до этого получили и сохранили в переменную BOT_TOKEN.

from telebot import TeleBot  # Токен чат-бота BOT_TOKEN = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' # Экземпляр класса TeleBot, через который будет происходить все взаимодействия с ботом bot = TeleBot(BOT_TOKEN)  # API-ключ сервисного аккаунта из Yandex.Cloud YC_STT_API_KEY = 'YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'

Заменяем значения переменных BOT_TOKEN и YC_STT_API_KEY на свой токен и API-ключ соответственно. Далее создаем файл «main.py«, где будет располагаться функционал самого чат-бота. В начале определим все импорты:

from config import bot from telebot.types import Message

Здесь мы импортировали объект класса TeleBot из раннее созданного «config.py» и класс Message из подмодуля types библиотеки. Данный класс представляет собой сообщение, отправленное в чат и содержит всю исчерпывающую информацию о нем.

Первое, что должен уметь чат-бот — это реагировать на команду /start, которая как раз и вызывается при запуске бота:

# Используем декоратор из объекта класса TeleBot,  # в который передаем параметр commands - список команд,  # при вызове которых будет вызываться данная функция @bot.message_handler(commands=['start'])  # Определяем функцию для обработки команды /start, она принимает объект класса Message - сообщение def start(message:Message):     # Отправляем новое сообщение, указав ID чата с пользователем и сам текст сообщения     bot.send_message(message.chat.id, "Йоу! Отправь мне аудиосообщение")

Здесь мы использовали декоратор из объекта bot, который даст нашей программе знать, что вот эту функцию start нужно вызывать только тогда, когда пользователь ввел команду /start.

Самая функция принимает лишь один аргумент — это экземпляр класса Message, то есть, в данном случае, это будет то самое сообщение с командой /start, которую юзер отправил сам или просто нажал «Запустить», войдя первый раз в нашего бота. В последнем случае она будет отправлена за него боту автоматически.

message_handler — это декоратор из библиотеки telebot, предназначенный для обработки входящих сообщений в чат-боте. Он позволяет задать функцию, которая будет вызываться автоматически, когда бот получает сообщение определенного типа или удовлетворяющее определенным условиям.

В самом конце файла размещаем следующий код, который запустит бот при запуске «main.py»:

if __name__ == "__main__":     bot.polling(non_stop=True)

Итак, теперь наш бот реагирует на его запуск пользователем и выпрашивает аудиосообщение:

Раз уж просит, то давайте дадим ему такую возможность и напишем еще одну функцию которая будет реагировать на отправку голосового сообщения:

Сперва определим функцию с другим декоратором, который будет реагировать уже не на команду, а тип сообщения — а именно голосовое:

@bot.message_handler(content_types=['voice']) def handle_voice(message:Message):

Давайте импортируем еще один класс из библиотеки, который будет представлять аудиосообщение:

from telebot.types import Voice

Теперь внутри функции нам нужно получить отправленное/пересланное аудиосообщение. Так как оно уже хранится на серверах Telegram, то мы можем просто получить путь к нему.

# Определяем объект класса Voice, который находится внутри параметра message # (он же объект класса Message) voice:Voice = message.voice # Получаем из него ID файла аудиосообщения file_id = voice.file_id # Получаем всю информацию о данном файле voice_file = bot.get_file(file_id) # А уже из нее достаем путь к файлу на сервере Телеграм в директории # с файлами нашего бота voice_path = voice_file.file_path

В данном случае переменная voice_path будет храниться в себе относительный путь к аудиофайлу, например: «voice/file_0.oga». То есть, есть сервер Telegram, а в нем директория со всеми файлами нашего бота — там есть папка voice где и лежит присланное аудиосообщение.

OGA — это расширение файла аудио, используемое на серверах Telegram. Этот формат, известный как Ogg Vorbis, обеспечивает хорошее качество звука и небольшие размеры файлов, что позволяет передавать голосовые сообщения в мессенджере с высокой четкостью и экономией трафика.

Однако, толку от такого пути для нас никакого. Давайте применим немного хитрости и получим абсолютный путь к сохраненному аудиосообщению. Для этого нам понадобится токен бота, который мы сохранили в «config.py»:

from config import BOT_TOKEN

И относительный путь файла, хранящийся в переменной voice_path:

file_base_url = f"https://api.telegram.org/file/bot{BOT_TOKEN}/{voice_path}"

Таким образом мы получим абсолютный путь к аудиофайлу на сервере Telegram вида:

https://api.telegram.org/file/botXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/voice/file_0.oga

Теперь у нас есть ссылка на аудиофайл и мы можем отправить ее Яндекс.Облаку, который попытается получить из данного аудиофайла текст.

Путешествие аудиосообщения

Создаем еще один файл, где мы будем взаимодействовать с сервисом Yandex.Cloud. Назовем его, к примеру «yandex_cloud.py«. Мы могли бы использовать какие-нибудь готовые библиотеки для данной задачи, но для такого простого функционала легче написать взаимодействие при помощи классического модуля requests. Импортируем его и API-ключ от Яндекс.Облака из конфига:

import requests from config import YC_STT_API_KEY

Определим переменную с адресом, на который будет идти запрос:

# URL для отправки аудиофайла на распознавание STT_URL = 'https://stt.api.cloud.yandex.net/speech/v1/stt:recognize'

И создаем функцию, которая будет принимать в качестве аргумента адрес аудиофайла на серверах Telegram и возвращать распознанный из него текст:

def get_text_from_speech(file_url):     # Выполняем GET-запрос по ссылке на аудиофайл     response = requests.get(file_url)      # Если запрос к серверу Telegram не удался...     if response.status_code != 200:         return None      # Получаем из ответа запроса наш аудиофайл     audio_data = response.content          # Создам заголовок с API-ключом для Яндекс.Облака, который пошлем в запросе     headers = {         'Authorization': f'Api-Key {YC_STT_API_KEY}'     }          # Отправляем POST-запрос на сервер Яндекс, который занимается расшифровкой аудио,     # передав его URL, заголовок и сам файл аудиосообщения     response = requests.post(STT_URL, headers=headers, data=audio_data)      # Если запрос к Яндекс.Облаку не удался...     if not response.ok:         return None      # Преобразуем JSON-ответ сервера в объект Python     result = response.json()     # Возвращаем текст аудиосообщения     return result.get('result')

Осталось только доработать функцию handle_voice из «main.py«

Осталось совсем чуть-чуть

Вернемся к модулю main и импортируем созданную функцию:

from yandex_cloud import get_text_from_speech

Продолжим код функции handle_voice и добавим пару строчек:

# Сохраняем текст аудиосообщения в перменную speech_text = get_text_from_speech(file_base_url) # Посылаем его пользователю в виде нового собщения bot.send_message(message.chat.id, speech_text)

Снова запускаем бота и, смотрим на результат:

Готово!

Полный код бота можно скачать на GitHub


ссылка на оригинал статьи https://habr.com/ru/articles/752864/

More powerful and intelligent task scheduling framework — Openjob 1.0.6 published

More powerful and intelligent task scheduling framework.

Introduction 

Openjob is a new  distributed task scheduling framework based on Akka architecture. Supports multiple cronjob, delay task, workflow, lightweight distributed computing, unlimited horizontal scaling, with high scalability and fault tolerance. Also has complete management, powerful alarm monitoring, and support multiple languages

  • Complete task log, and suppport storage (H2/Mysql/Elasticsearch).

  • detailed recording of task execution stack information

  • Provides task event monitoring alarms,  detailed alarm histories, and support notifications with Webhook,Wecom, ,Dingding and Feishu triggers. 

  • Designed with namespace, support button-level access  and easy to manage complex project.

  • Supports multiple programming languages, such as Java, Go, PHP and Python and various languages is very friendly. 

If you are looking for a high-performance distributed task scheduling framework that supports cronjob, delay task, lightweight computing, workflow, and supports multiple programming languages, then Openjob is definitely the way to go.

Feature

Openjob not only supports basic cronjob, but also provides delayed jobs, distributed computing, and workflow

Cronjob

  • Cronjob, support Unix Crontab expression

  • Second, execution cycle less than 60 seconds

  • Fixed rate, execute tasks at a fixed frequency with minute unit

Delay Task

  • Distributed, high-performance delay task  based on Redis, and providing rich reports and statistics

Distributed Computing 

  • Standalone, execute on a worker client

  • Broadcast, execute on all worker clients

  • Map, a map function can distribute big data to multiple machines for execution, like Hadoop map

  • MapReduce, MapReduce is an extension of the Map.After all map sub-tasks are completed, the Reduce method is executed, which can process the results and data of the task execution in the Reduce method.

  • Sharding, like Elastic-Job model, configure sharding numbers on the management, which can be scheduled to different client by sharding, and supports multiple languages.

Processor 

  • Processor, execute by function or class(support Java/Golang/PH)

  • HTTP, http request, used to periodically request an HTTP

  • Kettle, built-in Kettle command executor

  • Shell, shell script

Visual operations

  • Dashboard, rich task statistics and reports

  • Task history, task execution history records

  • Task log, complete task log, and suppport storage (H2/Mysql/Elasticsearch).

  • Task running stack, detailed recording of task execution stack information

Alarms and permissions

  • Provides task event monitoring alarms,  detailed alarm histories, and support notifications with Webhook,Wecom, Dingding and Feishu triggers. 

  • Designed with namespace, support button-level access  and easy to manage complex project.

Multiple languages

  • Java java and its frameworks, with native support.

  • Go golang support use go mod install

  • PHP PHP support use Golang agent to execute task by command mode 。Swoole frameworks support composer install.

  • Python python support use Golang agent to execute task by command mode

Application Scenario

Openjob is well-suited for business scenarios that have task schedule and delay task. such as every day to clean data and report generation. It is also suitable for lightweight computing, and Map/MapReduce can process big data computing. For complex task flows or workflow, it can design workflow with UI

Update

Openjob v1.0.6 add monitoring and alarm, fixed  known issues

Feature

[#141] Add alarm and monitoring(dingding/wecom/feishu/webhook)
[#141] Add execute timeout for cronjob
[#141] Add child fail status
[#144] Add next execute time
[#144] Add personal page
[#144] Add running status to dashboard

Bugfix

[#144] Fixed big task log

Optimize

[#144] Auto create index for Elasticsearch7
[#144] User default avatar

More


ссылка на оригинал статьи https://habr.com/ru/articles/752884/

Postgresso №8 (57)

Релизы PostgreSQL

Между этим обзором и предыдущим успели выйти бета3 и первый релиз-кандидат.

В PostgreSQL 16 Beta 3 закрыты 2 уязвимости, они касаются расширений и MERGE. В сообщении есть важные замечания для тех, кто использует BRIN-индексы для запросов, затрагивающих NULL-величины.

А вот это почти детективный сюжет: PostgreSQL: CVE-2020-21469 is not a security vulnerability.

Здесь поясняют:

Анализ нетипичных CVE, созданных 22 августа, показал, что в этот день было добавлено около 150 отчётов (123456) под идентификаторами, выданными в 2020-2022 годах. Идентификаторы охватывают большое число открытых проектов и, на первый взгляд, также интерпретируют обычные ошибки, не связанные с обработкой внешних данных, как опасные уязвимости.

Как обычно, синхронно обновились 15.4, 14.9, 13.12, 12.16, 11.21. И тогда же стало известно, что поддержка PostgreSQL 11 прекратится 9 ноября.

PostgreSQL: PostgreSQL 16 RC1

Исправлены баги. В том числе решена проблема производительности при параллельной работе COPY на одной таблице. Список исправлений можно посмотреть на странице актуальные проблемы (open items).

Можно уже посмотреть предварительные PostgreSQL16 Release Notes.

Выход в свет общедоступной (general availability) версии PostgreSQL 16 запланирован на 14-е сентября.

PostgreSQL 17: Часть 1 или Коммитфест 2023-07

Очередная статья-обзор Павла Лузанова. Предыдущие статьи о 16-й версии, привязанные к коммитфестам: 2022-072022-092022-112023-012023-03. Вот её английский вариант (как показывает практика, он востребован).

С апреля произошли некоторые важные изменения, на которые стоит обратить внимание. И начнем с потерь. Следующие разработки были отменены:

Release 16. Acknowledgments

Предварительный список вкладчиков PG 16 от Брюса. Много знакомых и знакомо звучащих фамилий.

«Не сравнивай: живущий несравним»

Но всё равно все всё со всем сравнивают, это в природе человеческой.

PlanetScale vs. Neon: the Continued Saga between MySQL and PostgreSQL

В блоге Bytebase интересное сравнение: дело дошло уже до бессерверных баз разных платформ. Автор статьи — Тяньчжоу (Tianzhou), он тимлид в Google Cloud SQL, поддерживал PostgreSQL и MySQL в Google, возглавлял отдел Database/DevTools/Collaboration в Ant Group.

Он взялся сравнивать эти новейшие базы по вот такому набору критериев:

В паре фраз он описывает эти базы так:

PlanetScale и Neon похожи друг на друга, а каждая из них похожа на MongoDB и Snowflake соответственно:

  • Как и MongoDB, PlanetScale использует архитектуру shared-nothing, предоставляет удобную СУБД-платформу, одержима ориентацией на разработчиков и рассказывает впечатляющие истории успеха.

  • Как и Snowflake, Neon воплощает новый подход shared-storage в застоявшуюся OLTP-архитектуру, использует диалект Postgres и тоже очень заботится о разработчиках.

И PlanetScale и Neon могут стать следующими MongoDB / Snowflake в мире современных РСУБД в качестве DBaaS. Чего мы давно ждём.

На сайте компании можно найти ещё сравнения:

А вот и куда более традиционный поджанр (и это опять Тяньчжоу):

Postgres vs. MySQL: a Complete Comparison in 2023

В них несколько отличающийся набор критериев. Воздержимся от цитирования — читайте.

Или не читайте, а пробегитесь по статье Отличия MySQL от PostgreSQL. Выбираем что лучше, PostgreSQL или MySQL Кирилла Косолапова, гендира Amvera. Пересечений со статьёй Тяньчжоу, мягко говоря, немало. Зато у того нет пункта Популярность в России. А у Кирилла нет JSON и CTE.

От пятнец к гео

Наполняем до краев: влияние порядка столбцов в таблицах на размеры баз данных PostgresQL

Оригинал — On Rocks and Sand | Optimizing Postgres Column Order принадлежит Шону Томасу (Shaun Thomas). Тому самому, с которого начались ПГ пятнецы — так мы стали переводить PG Phridays. Следующая инкарнация пятнец — PGSQL Phriday Райана Буза (Rayan Booz). Она медийно преуспела. Сейчас у нас уже PGSQL Phriday #012: What Excites You About PostgreSQL 16.

Впрочем, возможно, и сам Томас когда-то где-то увидел мелькнувшие Philosophical Phridays, или Physique Phriday, или ещё что-то в этом роде. И вдохновился.

Но ближе к делу. Статьи интересны не только сами по себе. Иногда ценная информация прилетает из комментария. К переводу этой статьи Шона вот что добавил Игорь Сухоруков:

Утилита postgres_dba прячет подобные запросы за своим интерфейсом, анализирует все таблицы в PostgreSQL и выдает рекомендации по порядку столбцов и оценку экономии занимаемого места в %. Я успешно использовал этот подход в проекте openstreetmap_h3, где объем БД уже полтерабайта и будет только увеличиваться со временем.

На это автор перевода ответил: Спасибо. Стоило написать статью ради этого комментария.

Игорь и собственную статью начинает такой фразой: в публикациях на хабре, чаще всего самое интересное в комментариях. Вот эта статья, там он делится опытом создания «на коленке» интерфейсов для испытания прототипов.

Мы-то Игоря Сухорукова знаем по выступлениям на PGConf.Russia, например: Как поместить весь мир в обычный ноутбук: PostgreSQL и OpenStreetMap на недавней PGConf.Russia 2023. А на днях появилась вот такая статья:

Способ залезть в «кишочки» операционной системы, Docker из PostgreSQL с помощью SQL

Это можно теперь сделать, используя postgres_osquery. Этот инструмент работает как интерфейс к опенсорсному средству osquery, который сам является SQL-интерфейсом к операционной системе. Игорь сочинил на Python хранимку run_osquery, которая получает на вход строку запроса к osquery, а на выходе выдает JSON, заменяя пустые строки на null. Результаты работы функции run_osquery можно сохранять в таблицы с помощью pg_cron по расписанию — советует автор, — или же запускать на каждом узле кластера CitusDB с помощью run_command_on_all_nodes().Или построить свое SIEM (Security information and event management) решение для хостов базы данных, используя только PostgreSQL.

Некоторые резонансные статьи

Squeeze the hell out of the system you have

Дэн Слиммон (Dan Slimmon) поделился личным опытом. База с монолитным SaaS-приложением росли, росли и перестали вмещаться по производительности в железо. Что же делать? Ну как что. Перейти на шардирование по записи или на микросервисы. Дело хорошее. Но — говорит Дэн, — это резкое усложнение, которое отольётся слёзками потом. Надо выжать из ситуации то, что можно выжать. И они выжали — максимальная загрузка процессора спустилась с 90% до 30%. Обошлись без шардов и микросервисов.

Но не все с этим согласны. Расползлась изрядная дискуссия на Hacker News. К концу все забыли про Postgres и спорили уже на философские темы оптимизации вообще.

What’s new with Postgres at Microsoft (August 2023)

Клэр Джордано (Claire Giordano, Microsoft Community Hub) обозревает с высоты птичьего полёта, как она выразилась, доработки Microsoft за 2023. Разработчики участвовали в патчах ядра, дорабатывали  Citus и pg_cron. Их решения обросли мускулами: многих заинтересует появление в этом списке PatroniPgBouncerpgcopydb. Вот удобный список-рубрикатор:

Поскольку деятельность Microsoft традиционно способствует выбросу адреналина у сообщества open source, не могли не начаться полуконспирологические дискуссии в духе ох не к добру эта их любовь к открытому коду. Вспомнили даже статью 2019 года Антона Сёмина Как Microsoft боролась с Open Source и почему ей пришлось полюбить Linux на Skillbox Media и аж 2007-го О том, почему монополия Google лучше, чем монополия Microsoft Виталия с почти зловещим ником @Tonatos.

PostgreSQL Logical Replication: Advantages, EDB’s Contributions and PG 16 Enhancements

Толковая статья Брайана Тёрифа (Bryan Turriff, EDB). В ней есть и азы, и история вопроса, и — как обещано — о вкладе EDB (2ndQuadrant), и преимущества, и недостатки, и куски кода, и о EDB Postgres Distributed (PGD), конечно, и о новом в PostgreSQL 16. Имя автора мне не знакомо. Это главный по маркетингу продуктов компании.

А вот другая новость от них:

EDB Open Sources Powerful Tool to Automate and Manage Postgres Deployments

Компания отдала в опен сорс свой инструмент для разворачивания и настройки высокодоступных (HA) кластеров. Называется он Trusted Postgres Architect (TPA). Подробности, возможно, в следующий раз.

Некоторые релизы

Supavisor: Scaling Postgres to 1 Million Connections

В статье Егора Романова, Чейза Грэнберри (Chase Granberry) и Станислава Мужика (Stanislav Muzhyk) рассказывается об их пулере. Он построен на Elixir (язык поверх Erlang, создатель — Жозе Валим (José Valim) участвовал и в этой разработке Supabase). Создавался пулер сразу с расчётом на мультиарендность (multi-tenancy). Линейные графики масштабируемости действительно впечатляют. Пока что вышла версия даже ещё не 1.х, а только 0.9.0.

PostgreSQL: PL/R 8.4.6

В этом релизе нет решающих изменений, но он, как минимум, демонстрирует, что проект жив-здоров. Поддерживает его Дейв Креймер (Dave Cramer), а в репозитории есть пометка со ссылкой на отца-основателя Джо Конвея (Joe/Joseph E Conway): forked from jconway/plr. Джо, похоже, проектом уже не занимается.

Версия учитывает изменения в PostgreSQL 16. Есть сборки для Windows с R версий 4.1.3 и 4.2.3. Все изменения здесь.

Hydra 1.0 beta

Hydra — колоночный Postgres для аналитиков. Разработчики утверждают: всё так хорошо распараллелено, что запросы, которые исполнялись бы недели, дадут ответ за минуты. И что можно смело делать запрос к миллиардам строк. Вакуум тоже оптимизирован под колонки. Хранение векторное. Следуя моде, там оптимизировали и поиск сходства (similarity search).

До этого 4 месяца испытывали версию со скромным названием 0.3.0 альфа. Обещают, что версия 1.0 GA (общедоступная) появится уже совсем скоро. Hydra построена поверх Citus Columnar, которая, в свою очередь, представляет собой модернизированную версию расширения cstore_fdw. Репозиторий здесь.

pgvector

Понемногу превращается в нашу постоянную рубрику. Возможно, под влиянием статьи Джонатана Каца Vectors are the new JSON in PostgreSQL.

Machine Learning challenge: Chihuahua vs Muffin with PostgreSQL and pgvector

Франческо Тизьё (Francesco Tisiot) в блоге DEV Community предлагает убедиться, что, вооружившись pgvector-ом можно легко и быстро разобраться с известным (в определённых ИИ-кругах) мемом: научить сеть отличать собачку чихуахуа (идентичней так: чиуауа) от печеньки с изюминами вместо глаз и носа. Пробовать питонные ноутбуки можно так: вот ноутбук, вот датасет для обучения, вот бесплатный PostgreSQL на Aiven.

An early look at HNSW performance with pgvector

Джонатан Кац (Jonathan Katz) признаётся, что не работал над HNSW — новой фичей 0.5.0, только тестировал её (но над pgvector работал). Делится сведениями о новом алгоритме, и приступает к тестированию performance/recall (полнота — переводят, например, здесь). Для сравнения Джонатан взял pg_embedding. В прошлом номере мы писали о расширении pg_embedding, которое разработал Neon. Теперь они его закоммитили в pgvector.

HNSW Indexes with Postgres and pgvector

Большая статья Кристофера Уинслета (Christopher Winslett, Crunchy Data). В ней красивые и наглядные схемы, разные аспекты работы с этим алгоритмом.

И вот, наконец, явился сам pgvector 0.5.0

В этой заметке о его выходе сообщают о том, к чему нас заранее готовил Джонатан. Но ещё и говорится об агрегате sum и функции l1_distance. Вот здесь новое.

После официального релиза Джонатан продолжил разъяснять и тестировать:

pgvector 0.5.0 Feature Highlights and HOWTOs

И не только он. В блоге Supabase её сотрудник Егор Романов (Egor Romanov) тоже продолжил тестирование ivfflat и HNSW:

pgvector v0.5.0: Faster semantic search with HNSW indexes

И повсюду продолжается добавление pgvector в свои продукты. pgvector добавили и в DBLab Engine, о них подробней ниже. Они (postgres.ai) объясняют это следованием очевидному тренду, ссылаясь на Google Cloud:

Unlock the power of gen AI with the pgvector PostgreSQL extension

Из сообщения Сандхьи Гхаи (Sandhya Ghai) и Балы Нарисимхана (Bala Narasimhan) следует не только то, что Cloud SQL for PostgreSQL и AlloyDB for PostgreSQL теперь поддерживают pgvector, но и что умеют работать с предобученными моделями через Vertex AI, которая сама по себе векторная база данных (не реляционная) и Matching Engine ANN service (ANN=Approximate Nearest Neighbor).

Database Lab => DBLab 3.4

DBLab 3.4: new name, SE installer, and lots of improvements

Эта своеобразная система клонирования баз нашему отделу Образования Postgres Professional совсем не чужая:

Для автоматической проверки заданий использовалась система, которую разработал наш коллега Илья Баштанов на основе Database Lab Engine. Она поддерживает пул тонких клонов PostgreSQL и запускает на них весь набор тестов в параллель. Вообще-то мы собираемся использовать эту систему для создания собственного SQL-блекджекатренажера, но это уже совсем другая история.

— Это Егор Рогов рассказывает о задачах третьего этапа олимпиады «IT-Планеты» по PostgreSQL.

Имя изменилось, да. Но, скорее, косметически: было Database Lab Engine, стало DBLab Engine. А вот внутренности изменились сильно: самое большое количество изменений за всё время существования — говорит Николай Самохвалов (Nikolay Samokhvalov), основатель и глава Postgres.ai. Вот здесь они все перечислены.

Появился платный инсталлятор для платной версии — DBLab Engine SE. Есть изменения в опциях конфигурации. Починили перезапуск контейнеров клонов. Теперь можно, например, запустить pg_upgrade -k внутри некоторого контейнера клона и сразу начать тестировать изолированно новую мажорную версию Postgres.

Образование, азы, наука, полезные развлечения

Итоговая конференция Летней школы Postgres Pro в Новосибирске : Компания Postgres Professional

11-го августа в Новосибирске прошла итоговая конференция Летней школы Postgres Professional на базе Новосибирского государственного университета. 15 финалистов представили 12 групповых и самостоятельных проектов, которые были посвящены средствам для тестирования BiHA, потоковой репликации и мультимастеру, развитию расширения pgsphere.

Открыта запись на бесплатный курс «Основы технологий баз данных» от Postgres Professional

Обучение пройдет с 16 сентября по 23 декабря в дистанционном формате. Программа построена на основе одноимённого учебного пособия коллектива авторов СПбГУ под руководством Бориса Асеновича Новикова. Курс читает технический директор ООО «Биллинговый центр» группы компаний cистемы «Город» — Давыдов Евгений Станиславович, чей опыт работы с базами данных составляет более 30 лет.

Customizing SQL Functions in PostgreSQL: Exploring Various Approaches

Давид Джан (David Zhang) из канадского отделения китайской High Go нередко появляется в наших обзорах. На этот раз он учит писать Hello World вычислять a+b разными способами:

  • SQL;

  • PL/pgSQL;

  • PL/Tcl (как ни странно);

  • PL/Perl;

  • PL/Python;

  • системная функция на C;

  • расширение.

Внедрение концепции матричного профиля в реляционную СУБД для интеллектуального анализа временных рядов

Авторы — Е. В. Иванова, М. Л. Цымблер. Напомним, что в номере Postgresso 8-9 (45-46) была маленькая главка о Михаиле: Интересный автор. Его работы по параллельным базам данных базируются на PostgreSQL, их можно найти на таких ресурсах как academia.edu (нужен эккаунт). Вот презентация о «Приручении слонов»: Embed Parallelism into PostgreSQL. Вот его страничка с курсами.

Fun with PostgreSQL Puzzles: Recursive Functions with Animations

Грег Сабино Муллани (Greg Sabino Mullane) из Crunchy Data продолжает развлекать пытливых постгресистов. Всё это опять в рамках Advent of Code, о котором мы немного говорили в номере Postgresso №6 (55).

Инструментарий, конечно, рекурсивными функциями не ограничивается. Грег задействует ещё и CTE, regexp_split_to_array и GREATEST/LEAST.

Конференции

Итоги PGConf.Сибирь 2023 в Томске

Среди итогов: доклады представили эксперты Postgres Professional, Sibedge, «Лукойл» и других компаний. И ещё: cотрудники Postgres Professional выступили с докладом в секции Лекции на траве.

Напоминаем о PGConf.СПб 2023

PGConf впервые пройдёт 25 сентября в Санкт-Петербурге. Конференция будет называться PGConf.СПб 2023. Место — Отель «Коринтия», Невский проспект., д.57.

А до этого состоится Стачка 2023 — IT-конференция в Ульяновске

И вот кто будет выступать на Стачке.

Неполное собрание сочинений Лоренца Альбе

Online Data Type Change in PostgreSQL

Изменить тип данных в большой таблице — это всегда головная боль. ALTER TABLE получает Exclusive lock, и таблица становится недоступна на чтение и запись. Лоренц Альбе (Laurenz Albe, Cybertec) рассказывает, как свести эти блокировки к минимуму.

Subqueries and performance in PostgreSQL

Как всегда над статьёй забавная картинка. Но это не главное. Он опять глубоко копает. В данном случае предлагает различать скалярные запросы и табулярные, разбирает разновидности подзапросов с точки зрения проблем производительности и их разрешения.

А в этой статье Лоренц ныряет в механизмы выделения памяти в C:

Memory Context: How Postgres Allocates Memory

Он рассказывает о концепте контекстов памяти как о средстве (попытке) обезопасить Postgres от утечек памяти, нередко случающихся в C; объясняет архитектуру контекстов и напоминает о представлении pg_backend_memory_contexts и функции pg_log_backend_memory_contexts(), которые дают возможность заглянуть в эти структуры.

Use HOT, so CLUSTER won’t rot in PostgreSQL

Совет «используйте хоты, дабы не протух кластер» — не исчерпывает содержание этой статьи Лоренца. Он объясняет механизмы распухания индексов и таблиц, которые подвергали кластеризации, объясняет связь эффективности HOT Updates и fillfactor, потом запускает pgbench и демонстрирует количественные зависимости.


На этом пока всё.


ссылка на оригинал статьи https://habr.com/ru/articles/752872/

Раритет из мира термопринтеров

Приветствую всех!

Думаю, все мы себе представляем, как работает термопринтер. Но неподвижная пластина и ряд нагревательных элементов не всегда были типичной конструкцией такого устройства.

Итак, в сегодняшней статье разберёмся, как устроен и работает термопринтер старого образца с подвижной головкой. Узнаем, как его подключить к микроконтроллеру и запустить. Традиционно будет много интересного.

Суть такова

Давным-давно я уже писал пост о подключении термопечатающей головки к микроконтроллеру. И там я обмолвился, что существовали и более старые экземпляры, где головка не была неподвижной и которые сильно отличаются по управлению. Конечно, с моей стороны было бы неправильно упустить из виду такой экземпляр, тем более, что у меня он есть.

Обзор оборудования

Так уж получилось, что мне в своё время достались остатки от мониторов пациента компании Criticare (модель мне неизвестна, но, судя по всему, это 506N3). Измерительное оборудование было утрачено, но осталась горсть плат, а также несколько термопринтеров.

Сама плата. Запустить её мне не удалось, при включении она просто выдаёт какую-то ошибку, попутно сообщая, что датчик пульсоксиметра не подключён. Тем не менее, распаивать её я не буду, когда-нибудь мы к ней ещё вернёмся.

Вообще, медицинское оборудование само по себе — отдельная тема, заслуживающая далеко не одной статьи. Многие из этих девайсов очень крутые, а некоторые технологические решения, что там можно встретить, сильно удивляют.

А вот и термопринтер. Это STP211J-192 от Seiko/Epson. Как ясно из названия, разрешение по горизонтали у него 192 точки. Отчётливо видны два шаговых двигателя, печатающая головка, направляющая, червячный вал.

С обратной стороны ничего интересного.

Слева привод головки. Также тут находится концевой выключатель крайнего её положения.

Справа привод протяжки бумаги.

Из других устройств, где применялись такие термопринтеры, можно вспомнить VeriFone PrintPak. И если в модели 350 стоит самый обычный, то в более старом 300 — именно тот, что у нас. Мною весьма активно ищется такой аппарат, но пока что найти его не вышло.

Что нужно, чтобы управлять таким принтером?

В отличие от ранее рассмотренных экземпляров, этот простой и дубовый как никогда.
Из оборудования у нас имеются два мотора, головка, а также датчик её положения. Всё, больше ничего нет. Сама по себе головка представляет сборку из восьми резисторов, которые и служат нагревателями, никакой управляющей логики в ней нет.

Таким образом, помимо драйвера двигателей, понадобятся также силовые ключи для управления головкой.

Моторы

Поскольку шаговые двигатели тут униполярные, для управления ими было решено использовать ULN2804A. Восьми выходов как раз хватит для двух шаговиков, использующихся в принтере.

В даташите на принтер отыскались и последовательности включения двигателя. Так что проблем возникнуть не должно.

Помня об этом, подключаем моторы к ULNке. Выводы 1-8 соединяются с портами контроллера.

ТПГ

В отличие от более совершенных моделей, где термопечатающая головка имела свой собственный драйвер и управлялась по последовательному интерфейсу, здесь применена обычная сборка из восьми нагревательных резисторов. Сама головка съёмная, в даташите даже описана процедура её замены.

Сопротивление этих резисторов отличается в зависимости от модели принтера и составляет от четырнадцати до восемнадцати ом.

Итак, схема для управления головкой получается примерно такая.

Контроллер

Для управления решил взять всем известную Arduino — просто из-за пятивольтовых уровней и встроенного USB-UART. У меня нет ответной части к такому шлейфу, поэтому я припаял МГТФ прямо к контактам. Они там очень крупные, можно спокойно подпаяться, не боясь поплавить шлейф.

Собираем всё вместе. Термопринтер просто идеально подошёл по размерам на макетку. На ней же разместились преобразователь питания, две ULNки и плата Arduino. Термоголовка питается от пяти вольт, но брать их от USB нельзя, во время печати ток может составлять больше двух ампер. Всё, можно начинать эксперименты.

Управление моторами

И для начала, конечно, разберёмся с приводами. Тут всё достаточно просто — шаг мотора головки сдвигает её на расстояние одного пикселя, шаг мотора протяжки бумаги прокручивает её на расстояние четверти пикселя. Функции для всего этого получились вот такие:

uint8_t currentPhase = 0; uint8_t paperCurrentPhase = 0;  void paperStep() {   switch (paperCurrentPhase) {     case 2:       digitalWrite(A0, LOW);       digitalWrite(A1, LOW);       digitalWrite(A2, HIGH);       digitalWrite(A3, HIGH);       break;     case 3:       digitalWrite(A0, LOW);       digitalWrite(A1, HIGH);       digitalWrite(A2, HIGH);       digitalWrite(A3, LOW);       break;     case 0:       digitalWrite(A0, HIGH);       digitalWrite(A1, HIGH);       digitalWrite(A2, LOW);       digitalWrite(A3, LOW);       break;     case 1:       digitalWrite(A0, HIGH);       digitalWrite(A1, LOW);       digitalWrite(A2, LOW);       digitalWrite(A3, HIGH);       break;   }   if (paperCurrentPhase == 3) paperCurrentPhase = 0;   else paperCurrentPhase++; }  void headStep(int8_t dir) {   if (dir == -1) {     switch (currentPhase) {       case 1:         digitalWrite(A4, LOW);         digitalWrite(A5, LOW);         digitalWrite(11, HIGH);         digitalWrite(12, HIGH);         break;       case 0:         digitalWrite(A4, LOW);         digitalWrite(A5, HIGH);         digitalWrite(11, HIGH);         digitalWrite(12, LOW);         break;       case 3:         digitalWrite(A4, HIGH);         digitalWrite(A5, HIGH);         digitalWrite(11, LOW);         digitalWrite(12, LOW);         break;       case 2:         digitalWrite(A4, HIGH);         digitalWrite(A5, LOW);         digitalWrite(11, LOW);         digitalWrite(12, HIGH);         break;     }   }   else if (dir == 1) {     switch (currentPhase) {       case 0:         digitalWrite(A4, LOW);         digitalWrite(A5, LOW);         digitalWrite(11, HIGH);         digitalWrite(12, HIGH);         break;       case 1:         digitalWrite(A4, LOW);         digitalWrite(A5, HIGH);         digitalWrite(11, HIGH);         digitalWrite(12, LOW);         break;       case 2:         digitalWrite(A4, HIGH);         digitalWrite(A5, HIGH);         digitalWrite(11, LOW);         digitalWrite(12, LOW);         break;       case 3:         digitalWrite(A4, HIGH);         digitalWrite(A5, LOW);         digitalWrite(11, LOW);         digitalWrite(12, HIGH);         break;     }   }   if (currentPhase == 3) currentPhase = 0;   else currentPhase++; }

В отличие от управления головкой, время выполнения тут не слишком критично, поэтому используются «медленные» digitalWrite. Для привода ТПГ также добавлена возможность задания направления.

Инициализация

Отдельно стоит упомянуть про действия после запуска. Сразу после подачи питания МК не знает, где сейчас находится головка. Поэтому необходимо выставить её в нулевое положение — гнать влево, пока она не упрётся в концевой выключатель. Дальше необходимо сделать ещё несколько добавочных шагов, так как датчик срабатывает несколько раньше, чем головка упирается в крайнее положение. Если же ноль уже стоит, выводим головку из него и проверяем, не разомкнулся ли концевик. Если даже после существенного числа шагов он всё равно замкнут, значит, на моторы не подаётся питание или просто нет контакта.
Делается это всё примерно так:

void headInit() {   if (!digitalRead(10)) headReturn();   else {     for (int i = 0; i < 50; i++) {       headStep(1);       delay(10);     }     if (digitalRead(10)) {       Serial.println("Head drive error");       while (1);;     }     else headReturn();   } }  void headReturn() {   while (!digitalRead(10)) {     headStep(-1);     delay(10);   }   for (int i = 0; i < 6; i++) {     headStep(-1);     delay(10);   } }

Вообще, в даташите было сказано о двух шагах после касания концевика. Но в моём случае механизм имел достаточно сильный люфт, так что для уверенного возврата каретки число шагов увеличил до шести. Только тогда она стала нормально вставать в крайнее положение.

Управление головкой

Теперь очередь нагревателей. Чтобы задать их состояние, используется следующая функция:

void headControl(uint8_t toHead) {   PORTD &= B00000011;   PORTB &= B11111100;   PORTD |= ((toHead << 2) & B11111100);   PORTB |= ((toHead >> 6) & B00000011); } 

Для удобства загрузки восьми бит сразу применена работа с портами через регистры.

Печать символов

Как известно, головка печатает строку символов за один проход, как в матричном принтере. То есть за раз прожигается вертикальная линия из восьми точек. А это значит, что тот самый шрифт из предыдущего поста про термопринтер подойдёт как нельзя лучше, не придётся разбираться с преобразованием строки символов в восемь горизонтальных линий. Поэтому для печати символа необходимо всего лишь разбить его на пять столбцов, а потом последовательно прожечь их, каждый раз сдвигая головку на один шаг.

Делается это примерно так:

void printChar(char input) {   uint8_t vertical8dots = 0x00;   for (int i = 0; i < 5; i++) {     vertical8dots = pgm_read_byte(&FontTable[input][i]);     headControl(vertical8dots);     delay(3);     headControl(0x00);     headStep(1);     delay(10);   }   headDriveOff(); }

Задержка перед отключением головки определяет яркость печати. Не стоит пытаться изменить её поднятием напряжения, иначе головка может сдохнуть.

Печать строки

Ну, где символы, там и строка. Делается это всё достаточно просто:

void printString(String toPrinter) {   int target = 0;   for (int i = 0; i < 20; i++) {     headStep(1);     delay(10);   }   if (toPrinter.length() > 18) target = 18;   else target = toPrinter.length();   for (int i = 0; i < target; i++) {     printChar(toPrinter[i]);     for (int n = 0; n < 3; n++) {       headStep(1);       delay(10);     }   }   headReturn();   headDriveOff(); }

Ничего сложного: прожигаем очередной символ, затем сдвигаем головку на некоторое число пикселей (в моём случае три) и так до конца строки. Затем возвращаем головку на место, и можно проматывать бумагу.

В итоге вся программа получилась такая:

#include "FontTable.h"  uint8_t currentPhase = 0; uint8_t paperCurrentPhase = 0;  void setup() {   for (int i = 2; i <= 9; i++) pinMode(i, OUTPUT);   pinMode(10, INPUT_PULLUP);   pinMode(11, OUTPUT);   pinMode(12, OUTPUT);   for (int i = 14; i <= 19; i++) pinMode(i, OUTPUT);   Serial.begin(115200);   headInit();   headDriveOff(); }  void paperStep() {   switch (paperCurrentPhase) {     case 2:       digitalWrite(A0, LOW);       digitalWrite(A1, LOW);       digitalWrite(A2, HIGH);       digitalWrite(A3, HIGH);       break;     case 3:       digitalWrite(A0, LOW);       digitalWrite(A1, HIGH);       digitalWrite(A2, HIGH);       digitalWrite(A3, LOW);       break;     case 0:       digitalWrite(A0, HIGH);       digitalWrite(A1, HIGH);       digitalWrite(A2, LOW);       digitalWrite(A3, LOW);       break;     case 1:       digitalWrite(A0, HIGH);       digitalWrite(A1, LOW);       digitalWrite(A2, LOW);       digitalWrite(A3, HIGH);       break;   }   if (paperCurrentPhase == 3) paperCurrentPhase = 0;   else paperCurrentPhase++; }  void headStep(int8_t dir) {   if (dir == -1) {     switch (currentPhase) {       case 1:         digitalWrite(A4, LOW);         digitalWrite(A5, LOW);         digitalWrite(11, HIGH);         digitalWrite(12, HIGH);         break;       case 0:         digitalWrite(A4, LOW);         digitalWrite(A5, HIGH);         digitalWrite(11, HIGH);         digitalWrite(12, LOW);         break;       case 3:         digitalWrite(A4, HIGH);         digitalWrite(A5, HIGH);         digitalWrite(11, LOW);         digitalWrite(12, LOW);         break;       case 2:         digitalWrite(A4, HIGH);         digitalWrite(A5, LOW);         digitalWrite(11, LOW);         digitalWrite(12, HIGH);         break;     }   }   else if (dir == 1) {     switch (currentPhase) {       case 0:         digitalWrite(A4, LOW);         digitalWrite(A5, LOW);         digitalWrite(11, HIGH);         digitalWrite(12, HIGH);         break;       case 1:         digitalWrite(A4, LOW);         digitalWrite(A5, HIGH);         digitalWrite(11, HIGH);         digitalWrite(12, LOW);         break;       case 2:         digitalWrite(A4, HIGH);         digitalWrite(A5, HIGH);         digitalWrite(11, LOW);         digitalWrite(12, LOW);         break;       case 3:         digitalWrite(A4, HIGH);         digitalWrite(A5, LOW);         digitalWrite(11, LOW);         digitalWrite(12, HIGH);         break;     }   }   if (currentPhase == 3) currentPhase = 0;   else currentPhase++; }  void lineFeed() {   for (int i = 0; i < 48; i++) {     paperStep();     delay(10);   }   paperDriveOff(); }  void headReturn() {   while (!digitalRead(10)) {     headStep(-1);     delay(10);   }   for (int i = 0; i < 6; i++) {     headStep(-1);     delay(10);   } }  void headInit() {   if (!digitalRead(10)) headReturn();   else {     for (int i = 0; i < 50; i++) {       headStep(1);       delay(10);     }     if (digitalRead(10)) {       Serial.println("Head drive error");       while (1);;     }     else headReturn();   } }  void headDriveOff() {   digitalWrite(A4, LOW);   digitalWrite(A5, LOW);   digitalWrite(11, LOW);   digitalWrite(12, LOW); }  void paperDriveOff() {   digitalWrite(A0, LOW);   digitalWrite(A1, LOW);   digitalWrite(A2, LOW);   digitalWrite(A3, LOW); }  void headControl(uint8_t toHead) {   PORTD &= B00000011;   PORTB &= B11111100;   PORTD |= ((toHead << 2) & B11111100);   PORTB |= ((toHead >> 6) & B00000011); }  void printChar(char input) {   uint8_t vertical8dots = 0x00;   for (int i = 0; i < 5; i++) {     vertical8dots = pgm_read_byte(&FontTable[input][i]);     headControl(vertical8dots);     delay(3);     headControl(0x00);     headStep(1);     delay(10);   }   headDriveOff(); }  void printString(String toPrinter) {   int target = 0;   for (int i = 0; i < 20; i++) {     headStep(1);     delay(10);   }   if (toPrinter.length() > 18) target = 18;   else target = toPrinter.length();   for (int i = 0; i < target; i++) {     printChar(toPrinter[i]);     for (int n = 0; n < 3; n++) {       headStep(1);       delay(10);     }   }   headReturn();   headDriveOff(); }  void loop() {   String inputString = Serial.readString();    if (inputString.length() > 0)   {     printString(inputString);     lineFeed();   } }

Пробуем что-то напечатать… и оно даже работает! К слову говоря, шрифт очень сильно напоминает тот, что выдаёт матричный принтер. Справа на фото как раз такая распечатка — сходство весьма сильное.

И я даже записал видео с этим:

Двунаправленная печать

А что, если реализовать печать как в матричном принтере — при каждом проходе каретки? Официально этот механизм такое не поддерживает, но ничего не мешает это попробовать.
Для того, чтобы такое реализовать, необходимо поменять алгоритм печати: будем прожигать строку не посимвольно, а всю разом. Для этого создадим массив, куда запишем все символы вместе с пробелами сразу, а потом будем его печатать. Получилось примерно следующее:

void printString(String toPrinter) {   uint8_t index = 0;   uint8_t toHead[192];   for (int i = 0; i < 192; i++) toHead[i] = 0x00;   int target = 0;   for (int i = 0; i < 20; i++) {     headStep(1);     delay(10);   }   if (toPrinter.length() > 18) target = 18;   else target = toPrinter.length();   for (int i = 0; i < target; i++) {     for (int n = 0; n < 5; n++) {       toHead[index] = pgm_read_byte(&FontTable[toPrinter[i]][n]);       index++;     }     index += 3;   }   for (int i = 0; i < 146; i++) {     headControl(toHead[i]);     delay(3);     headControl(0x00);     headStep(1);     delay(10);   }   headMoveDirection = -1;   headDriveOff(); }

Как оказалось, эффективная ширина печати намного меньше 192 пикселей, а без отступов по краям распечатка выглядит так себе. Тем не менее, размер массива в 192 байта я оставил, для совместимости с другими модификациями этого принтера (ну, или если кому-то захочется печатать без полей).

Запускаем и убеждаемся, что всё работает как надо. Как нетрудно догадаться, алгоритм печати в обратном направлении совершенно идентичен:

void printStringReversed(String toPrinter) {   uint8_t index = 0;   uint8_t toHead[192];   for (int i = 0; i < 192; i++) toHead[i] = 0x00;   int target = 0;   if (toPrinter.length() > 18) target = 18;   else target = toPrinter.length();   for (int i = 0; i < target; i++) {     for (int n = 0; n < 5; n++) {       toHead[index] = pgm_read_byte(&FontTable[toPrinter[i]][n]);       index++;     }     index += 3;   }     for (int i = 148; i >= 0; i--) {     headControl(toHead[i]);     delay(3);     headControl(0x00);     headStep(-1);     delay(10);   }   headReturn();   headDriveOff();   headMoveDirection = 1; }

Чтобы сделать печать максимально простой, добавил отдельную функцию, где бы выбиралось нужное направление:

void processPrinting(String input) {   if (headMoveDirection == 1) printString(input);   else if (headMoveDirection == -1) printStringReversed(input);   lineFeed(); }

Работает. Но тут я столкнулся уже с чисто конструктивными ограничениями: как бы я ни подкручивал общее число шагов, заставить строки быть точно на одном уровне не вышло. Люфт механизма всё же даёт о себе знать.

Получилось примерно так:

На видео заметен ещё один глюк: при печати в обратном направлении теряются первые два символа. Это косяк не алгоритма печати, а исключительно функции для вывода этой таблицы символов.

Вот как-то так

Понятное дело, что в наши дни этот принтер — скорее игрушка, чем действительно рабочий девайс. И для реальных проектов давно уже существуют более простые в управлении принтеры. Тем не менее, запустить такое было реально интересно. А некоторое сходство с матричным принтером ещё больше добавляет крутизны этому девайсу.

Такие дела.


Возможно, захочется почитать и это:


ссылка на оригинал статьи https://habr.com/ru/articles/752870/

Как конфигурировать функциональность мобильных приложений

При разработке мобильных приложений часто возникает необходимость иметь возможность включать/выключать или параметризировать функциональность по разным причинам как при изначальной сборке приложения, так и в уже собранном или даже выпущенном приложении. В этой статье мы рассмотрим разные подходы к конфигурированию функциональности мобильных приложений и их преимущества и недостатки.

Конфигурирование функциональности может быть полезно для разных целей, например:

  • Добавления отладочной панели или ключей для тестирования приложения

  • Управления новой функциональностью, которая еще не протестирована или не готова к релизу

  • Запуска AB-тестов для проверки продуктовых гипотез и измерения эффективности изменений

  • Адаптации приложения к разным регионам, языкам или платформам

  • Реагирования на изменения внешних условий, например рекламных кампаний или доступности сервисов

В зависимости от того, на каком этапе жизненного цикла приложения мы хотим конфигурировать его функциональность, мы можем использовать разные способы

Конфигурация на уровне сборки

Самый простой вариант конфигурации — это использование параметров, которые задаются при сборке приложения. Такие параметры могут, например, отвечать за добавление в код приложения кода отладочной панели, или для задания отладочных ключей. Тогда для сборки отладочной версии приложения может использоваться один конфигурационный файл, а для релизной — другой.

Пример конфигурационного файла (Android приложение):

// gradle.properties isDebugPanelEnabled=true
// build.gradle dependencies {     ...     if (getProperty("isDebugPanelEnabled")) {         implementation ':libs:debug'     }     ... } 

Главное преимущество такой конфигурации — это простота реализации, но, когда приложение уже собрано, изменить значения параметров уже не получится. Такая конфигурация не позволяет управлять функциональностью для конкретных пользователей или групп пользователей.

Локальная конфигурация

Часто бывает необходимо иметь версию приложения, в которой можно включать/выключать и настраивать существующую функциональность на одной сборке. Например, такая необходимость часто возникает при тестировании приложения, когда в одной сборке есть как отлаженная функциональность, так и новая, непротестированная. Такую функциональность хочется “убрать под флаг”, чтобы в случае обнаружения в новой функциональности дефектов не блокировать релизный процесс мобильного приложения.

Для реализации такой возможности необходимо разработать отдельную отладочную панель — экран, на котором можно управлять состоянием флагов. Хороший пример — отладочная панель в ОС Android. Она позволяет, например, настраивать плотность пикселей на устройстве или включить отображение границ UI-компонентов.

Отладочная панель в ОС Android

Отладочная панель в ОС Android

Преимущество локальной конфигурации — это возможность изменять значения флагов в любой момент на конкретном устройстве. Недостаток — это необходимость разработки и поддержки отладочной панели, а также отсутствие возможности управлять функциональностью удаленно.

Удалённая конфигурация

В некоторых случаях необходимо управлять параметрами удаленно с сервера. В этом случае на старте приложение запрашивает конфигурацию у специального сервера и использует ее для включения/выключения функциональности и ее настройки. Такая необходимость может появиться если, например, необходимо выложить приложение с функциональностью в выключенном состоянии, а затем включить ее в определенный момент времени, например к началу рекламной кампании.

Дополнительная механика, которую можно поддержать для удаленных конфигураций — это раздача конфигураций “на процент”. В этом случае для части пользователей возвращаются одни значения, а остальным — другие. Это можно использовать, например, для постепенного включения новой функциональности и соответствующего увеличения нагрузки на новые методы API, чтобы убедиться, что сервер справится с нагрузкой.

Дополнительно для удаленных флагов можно предусмотреть версионирование, так как при обнаружении дефектов в приложении и их последующем исправлении при переключении флага функциональность с дефектом станет доступна пользователям, если они не обновили приложение.

Этот вариант очень удобен для запуска функциональности в привязке ко времени, а также для поэтапной раскатки функциональности на часть пользователей. Тем не менее здесь необходимо реализовывать клиент-серверное взаимодействие и административную панель для управления состояниями флагов. В самых простых случаях для реализации удаленной конфигурации можно использовать готовые решения, например, Firebase Remote Config.

Конфигурация AB-теста

Однако, самым распространенным для сложных продуктов сценарием, подразумевающим включение/выключение функциональности и управление ее параметрами, является запуск AB-экспериментов. С точки зрения приложения этот вариант очень похож на вариант с удаленной конфигурацией, но логика на серверной стороне отличается. В рамках AB-теста сегмент пользователей делится на группы: контрольную, которая получает исходную версию продукта, и тестовую, которая получает версию продукта с новой функциональностью. Каждая группа пользователей получает свою версию конфигурации и соответственно, по-разному сконфигурированную функциональность в приложении. По каждой группе пользователей измеряются продуктовые метрики, и если версия приложения с тестовой конфигурацией признается лучшей, то такой эксперимент принимают и версия приложения с новой функциональностью становится исходной версией для новых экспериментов. Вопросы выбора сегмента пользователей для AB-эксперимента и их дальнейшее разбиение на группы эксперимента вынесем за рамки этой статьи.

Реализация административного раздела для создания AB-экспериментов, настройка их параметров, а также настройка инструментов для анализа их результатов — отдельная и сложная задача. Тем не менее для самых простых случаев также можно использовать готовые решения, например, Firebase.

Всё вместе

Очень часто все эти способы управлять функциональностью приложения реализуют вместе, и возникает вопрос, а какой источник для значения параметра выбрать, если значения из разных источников отличаются.

Локальная конфигурация — это самый удобный способ изменить значение параметров на конкретном устройстве. Поэтому этот источник можно считать самым “сильным” и в первую очередь использовать значение оттуда. Если в локальной конфигурации значение параметра не переопределено, то необходимо использовать значение, полученное с сервера.

Удаленная конфигурация и конфигурация AB-теста объединяет то, что обе они запрашиваются с сервера и с точки зрения мобильного приложения часто неразличимы. Эти варианты редко используются вместе, но такие сценарии возможны. Например, когда новую функциональность необходимо запустить через AB-эксперимент, но при этом иметь возможность отключать ее совсем без остановки теста, если, например, используемый для работы функциональности сервис временно отказал. В таких случаях предпочтение следует отдавать значению удаленной конфигурации.

Параметры из конфигурации на уровне сборки очень часто используются в качестве значений по умолчанию, поэтому используются, если ни в одном другом источнике они не переопределены.

Выбор источника значения параметра

Выбор источника значения параметра

Выводы

В статье были рассмотрены подходы к конфигурированию функциональности мобильных приложений как на этапе сборки, так и когда приложение уже собрано или даже выпущено. Для реализации этих подходов есть готовые решения, однако в больших командах часто используются собственные решения, которые учитывают специфику конкретных команд и продуктов. При использовании таких конфигураций можно упростить процесс разработки и тестирования мобильного приложения, а также обеспечить возможность проверки продуктовых гипотез через AB-тесты.


ссылка на оригинал статьи https://habr.com/ru/articles/752892/